use std::fmt;
use std::io;

use serde_json::Value;
use thiserror::Error;

/// Result alias used across the crate.
pub type Result<T, E = FastMcpError> = std::result::Result<T, E>;

/// Errors surfaced by the FastMCP Rust prototype.
#[derive(Debug, Error)]
pub enum FastMcpError {
    #[error("tool '{0}' already registered")]
    DuplicateTool(String),
    #[error("tool '{0}' not found")]
    ToolNotFound(String),
    #[error("resource '{0}' already registered")]
    DuplicateResource(String),
    #[error("resource '{0}' not found")]
    ResourceNotFound(String),
    #[error("prompt '{0}' already registered")]
    DuplicatePrompt(String),
    #[error("prompt '{0}' not found")]
    PromptNotFound(String),
    #[error("invalid invocation payload: {0}")]
    InvalidInvocation(String),
    #[error("handler error: {0}")]
    HandlerError(String),
    #[error("serialization error: {0}")]
    Serialization(#[from] serde_json::Error),
    #[error("io error: {0}")]
    Io(#[from] io::Error),
}

impl FastMcpError {
    /// Convenience helper for building handler errors from any displayable type.
    pub fn handler_error(err: impl fmt::Display) -> Self {
        Self::HandlerError(err.to_string())
    }
}

/// Helper for validating the presence of a JSON object.
pub fn expect_object<'a>(
    payload: &'a Value,
    context: &'static str,
) -> Result<&'a serde_json::Map<String, Value>> {
    payload
        .as_object()
        .ok_or_else(|| FastMcpError::InvalidInvocation(format!("{context} expects a JSON object")))
}
